class EU4EncoderDecoder {
    constructor() {
        // 定义 cp1252 到 UTF - 8 的映射
        this.cp1252_to_utf8_map = {
            0x80: 0x20AC, 0x82: 0x201A, 0x83: 0x0192, 0x84: 0x201E,
            0x85: 0x2026, 0x86: 0x2020, 0x87: 0x2021, 0x88: 0x02C6,
            0x89: 0x2030, 0x8A: 0x0160, 0x8B: 0x2039, 0x8C: 0x0152,
            0x8E: 0x017D, 0x91: 0x2018, 0x92: 0x2019, 0x93: 0x201C,
            0x94: 0x201D, 0x95: 0x2022, 0x96: 0x2013, 0x97: 0x2014,
            0x98: 0x02DC, 0x99: 0x2122, 0x9A: 0x0161, 0x9B: 0x203A,
            0x9C: 0x0153, 0x9E: 0x017E, 0x9F: 0x0178
        };
        // 定义 UTF - 8 到 cp1252 的逆向映射
        this.utf8_to_cp1252_map = {};
        for (const key in this.cp1252_to_utf8_map) {
            const value = this.cp1252_to_utf8_map[key];
            this.utf8_to_cp1252_map[value] = parseInt(key, 16);
        }
    }

    cp1252_to_utf8(char) {
        return this.cp1252_to_utf8_map[char] || char;
    }

    utf8_to_cp1252(char) {
        return this.utf8_to_cp1252_map[char] || char;
    }

    encode_char(char, to_utf8 = true, new_version = true) {
        const codePoint = char.codePointAt(0);
        if (codePoint < 256) {
            return char;
        }
        const hexCode = codePoint.toString(16).padStart(4, '0');
        let low = parseInt(hexCode.slice(-2), 16);
        let high = parseInt(hexCode.slice(0, 2), 16);

        const lowByteOffset = new_version? 14 : 15;
        const highByteOffset = -9;

        const internalChars = [
            0x00, 0x0A, 0x0D,
            0x20,
            0x22, 0x24,
            0x40, 0x5B, 0x5C,
            0x7B, 0x7D, 0x7E, 0x80,
            0xA3, 0xA4, 0xA7, 0xBD,
            0x3B,
            0x5D,
            0x5F,
            0x3D,
            0x23
        ];

        if (to_utf8) {
            internalChars.push(0x2F);
            if (!new_version) {
                const index = internalChars.indexOf(0x20);
                if (index!== -1) {
                    internalChars.splice(index, 1);
                }
            }
        }

        let escapeChr = 0x10;
        if (internalChars.includes(high)) {
            escapeChr += 2;
        }
        if (internalChars.includes(low)) {
            escapeChr += 1;
        }

        if (escapeChr === 0x11) {
            low += lowByteOffset;
        } else if (escapeChr === 0x12) {
            high += highByteOffset;
        } else if (escapeChr === 0x13) {
            low += lowByteOffset;
            high += highByteOffset;
        }

        if (to_utf8) {
            low = this.cp1252_to_utf8(low);
            high = this.cp1252_to_utf8(high);
        }

        return String.fromCodePoint(escapeChr, low, high);
    }

    decode_char(encodedChar, to_utf8 = true, new_version = true) {
        if (encodedChar.length === 1) {
            return encodedChar;
        }
        const escapeChr = encodedChar.codePointAt(0);
        let low = encodedChar.codePointAt(1);
        let high = encodedChar.codePointAt(2);

        if (to_utf8) {
            low = this.utf8_to_cp1252(low);
            high = this.utf8_to_cp1252(high);
        }

        const lowByteOffset = new_version? 14 : 15;
        const highByteOffset = -9;

        if (escapeChr === 0x11) {
            low -= lowByteOffset;
        } else if (escapeChr === 0x12) {
            high -= highByteOffset;
        } else if (escapeChr === 0x13) {
            low -= lowByteOffset;
            high -= highByteOffset;
        }

        const hexCode = high.toString(16).padStart(2, '0') + low.toString(16).padStart(2, '0');
        const codePoint = parseInt(hexCode, 16);
        return String.fromCodePoint(codePoint);
    }

    encode_string(s, to_utf8 = true, new_version = true) {
        let encoded = '';
        for (let i = 0; i < s.length; i++) {
            const char = s[i];
            encoded += this.encode_char(char, to_utf8, new_version);
        }
        return encoded;
    }

    decode_string(encodedStr, to_utf8 = true, new_version = true) {
        let decoded = '';
        let i = 0;
        while (i < encodedStr.length) {
            const char = encodedStr[i];
            const codePoint = char.codePointAt(0);
            if (32 <= codePoint && codePoint <= 126) {
                decoded += char;
                i++;
            } else if (codePoint < 0x10) {
                decoded += char;
                i++;
            } else {
                const encodedChar = encodedStr.slice(i, i + 3);
                decoded += this.decode_char(encodedChar, to_utf8, new_version);
                i += 3;
            }
        }
        return decoded;
    }

    encode(s) {
        return this.encode_string(s);
    }

    decode(s) {
        return this.decode_string(s);
    }
}

class EU4EncoderDecoderTest {
    test_encode() {
        console.log("加密测试");
        const encoderDecoder = new EU4EncoderDecoder();
        const originString = "留任 $MONARCH$";
        const encodeString = encoderDecoder.encode(originString);
        const decodeString = encoderDecoder.decode(encodeString);
        console.log(`Original: ${originString}, Encoded: ${encodeString}, Decoded: ${decodeString}`);
    }

    test_decode() {
        console.log("解密测试");
        const encoderDecoder = new EU4EncoderDecoder();
        const encodeString = "$MONARCHTITLE$ $MONARCH$«ˆNg³R";
        const decodeString = encoderDecoder.decode(encodeString);
        console.log(decodeString);
    }
}

const test = new EU4EncoderDecoderTest();
test.test_encode();
test.test_decode();    